-------------------------------------------------------------------------------
-- control_unit_rtl.vhd
-------------------------------------------------------------------------------
--
-- This file is part of SKUMLI.
-- Copyright (C) 2011 Davide Giuseppe Monaco (black.ralkass@gmail.com)
--
-- SKUMLI is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- SKUMLI is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with SKUMLI.  If not, see <http://www.gnu.org/licenses/>.
--
-------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
-------------------------------------------------------------------------------
architecture rtl of control_unit is

  signal state    : std_logic_vector(3 downto 0);
  signal aluop    : std_logic_vector(1 downto 0);
  signal cuzero   : std_logic;

begin -- rtl
-------------------------------------------------------------------------------
  beqctr: process (opcode)

    variable x : std_logic;

  begin -- beqctr

    case opcode is
      -- beqz
      when "010101" =>
        x := '1';

      -- bnez
      when "010110" =>
        x := '0';

      when others =>
        x := '0';

    end case;

    cuzero <= x;

  end process beqctr;
-------------------------------------------------------------------------------
  aluctr: process (opcode)

    variable op : std_logic_vector(1 downto 0);

  begin -- aluctr

    case opcode is
      -- add, addi, lw, sw
      when "001001" | "010001" | "010011" | "010100" =>
        op := "00";

      -- sub, subi, beqz, bnez
      when "001010" | "010010" | "010101" | "010110" =>
        op := "01";

      -- slt
      when "001011" =>
        op := "10";

      -- sgt
      when "001100" =>
        op := "11";

      when others =>
        op := "00";

    end case;

    aluop <= op;

  end process aluctr;
-------------------------------------------------------------------------------
  sync: process (clk, rst, state, opcode, cc_rdy)

    variable s : std_logic_vector(3 downto 0);

  begin -- sync

    if rst = '1' then
      s := (others => '0');

    elsif clk'event and clk = '1' then

      case state is

        -- IDLE
        when "0000" =>
          if cc_rdy = '1' then
            s := "0001"; -- -> FETCH

          else
            s := "0000"; -- -> IDLE if cache controller not ready

          end if;

        -- FETCH
        when "0001" =>
          s := "1100";

          -- INTERFETCH
        when "1100" => 
          if cc_rdy = '1' then
            s := "0011"; -- -> DECODE
          else
            s := "1100";
          end if;

        -- DECODE
        when "0011" =>

          case opcode is

            -- addi subi lw sw
            when "010001" | "010010" | "010011" | "010100" =>
              s := "0111"; -- -> EXEC I-TYPE 

            -- add sub slt sgt
            when "001001" | "001010" | "001011" | "001100" =>
              s := "1011"; -- -> EXEC R-TYPE

            -- beqz bnez
            when "010101" | "010110" =>
              s := "0010"; -- -> EXEC BRANCH

            -- j
            when "100001" =>
              s := "1000"; -- -> EXEC JUMP

            when others =>
              s := "1101"; -- -> REFRESH CURRENT ADDRESS

          end case;

        -- EXEC I-TYPE
        when "0111" =>

          case opcode is

            -- addi or subi
            when "010001" | "010010"  => 
              s := "0101"; -- -> WRITE BACK I-TYPE

            -- lw
            when "010011" => 
              if cc_rdy = '1' then
                s := "1111"; -- -> MEM ACCESS READ

              else
                s := "0111"; -- -> STAY if cache controller not ready

              end if;
           
            -- sw
            when "010100" => 
              if cc_rdy = '1' then
                s := "0110"; -- -> MEM ACCESS WRITE

              else
                s := "0111"; -- -> STAY if cache controller not ready

              end if;

            -- return idle on violation
            when others =>
              s := (others => '0'); 

          end case;

        -- EXEC R-TYPE
        when "1011" =>
          s := "1010"; -- -> WRITE BACK R-TYPE

        -- WRITE BACK I-TYPE
        -- WRITE BACK R-TYPE
        -- WRITE BACK LOAD
        -- EXEC BRANCH
        -- EXEC JUMP
        when "0101" | "1010" | "1001" | "0010" | "1000" =>
          if cc_rdy = '1' then
            s := "0001"; -- -> FETCH
          else
            s := state;
          end if;

        -- MEM ACCESS WRITE
        when "0110" =>
          if cc_rdy = '1' then
            s := "0100";
          else
            s := state;
          end if;

        -- MEM ACCESS READ
        when "1111" =>
          if cc_rdy = '1' then
            s := "1001"; -- -> WRITE BACK LOAD
          else
            s := "1111";
          end if;

          -- this intermediate state is used to refresh CURR ADDR register
        when "1101" =>
          s := "0001"; -- -> FETCH

        when others => 
          --s := (others => '0'); -- return idle on violation
          s := "0001"; -- -> FETCH

      end case;

    end if;

    state <= s;

  end process sync;
-------------------------------------------------------------------------------
  comb: process (state)
  begin

    case state is
      -- IDLE
      when "0000" =>
        m_pc_din_sel  <= "00";
        cu_zero       <= '0';
        branch        <= '0';
        pc_we_s       <= '0';
        ca_addr_sel   <= "00";
        ca_we         <= '0';
        m_cc_addr_sel <= '0';
        cc_rw         <= '0';
        cc_cs         <= '0';
        md_we         <= '0';
        m_data_sel    <= '0';
        ir_we         <= '0';
        alu_out_we    <= '0';
        tg_we         <= '0';
        alu_op        <= "00";
        m_opd_b_sel   <= "00";
        m_opd_a_sel   <= '0';
        opd_b_we      <= '0';
        opd_a_we      <= '0';
        rf_we         <= '0';
        m_rd_sel      <= '0';

      -- FETCH
      when "0001" =>
        m_pc_din_sel  <= "01";
        cu_zero       <= '0';
        branch        <= '0';
        pc_we_s       <= '1';
        ca_addr_sel   <= "00";
        ca_we         <= '1';
        m_cc_addr_sel <= '0';
        cc_rw         <= '0';
        cc_cs         <= '1';
        md_we         <= '0';
        m_data_sel    <= '0';
        ir_we         <= '1';
        alu_out_we    <= '0';
        tg_we         <= '0';
        alu_op        <= "00";
        m_opd_b_sel   <= "10";
        m_opd_a_sel   <= '0';
        opd_b_we      <= '0';
        opd_a_we      <= '0';
        rf_we         <= '0';
        m_rd_sel      <= '0';

        -- INTERFETCH
      when "1100" =>
        m_pc_din_sel  <= "01";
        cu_zero       <= '0';
        branch        <= '0';
        pc_we_s       <= '0';
        ca_addr_sel   <= "00";
        ca_we         <= '0';
        m_cc_addr_sel <= '0';
        cc_rw         <= '0';
        cc_cs         <= '1';
        md_we         <= '0';
        m_data_sel    <= '0';
        ir_we         <= '1';
        alu_out_we    <= '0';
        tg_we         <= '0';
        alu_op        <= "00";
        m_opd_b_sel   <= "10";
        m_opd_a_sel   <= '0';
        opd_b_we      <= '0';
        opd_a_we      <= '0';
        rf_we         <= '0';
        m_rd_sel      <= '0';

      -- DECODE
      when "0011" =>
        m_pc_din_sel  <= "00";
        cu_zero       <= '0';
        branch        <= '0';
        pc_we_s       <= '0';
        ca_addr_sel   <= "00";
        ca_we         <= '0';
        m_cc_addr_sel <= '0';
        cc_rw         <= '0';
        cc_cs         <= '0';
        md_we         <= '0';
        m_data_sel    <= '0';
        ir_we         <= '0';
        alu_out_we    <= '0';
        tg_we         <= '1';
        alu_op        <= "00";
        m_opd_b_sel   <= "11";
        m_opd_a_sel   <= '0';
        opd_b_we      <= '1';
        opd_a_we      <= '1';
        rf_we         <= '0';
        m_rd_sel      <= '0';

      -- EXEC I-TYPE
      when "0111" =>
        m_pc_din_sel  <= "00";
        cu_zero       <= '0';
        branch        <= '0';
        pc_we_s       <= '0';
        ca_addr_sel   <= "00";
        ca_we         <= '0';
        m_cc_addr_sel <= '0';
        cc_rw         <= '0';
        cc_cs         <= '0';
        md_we         <= '0';
        m_data_sel    <= '0';
        ir_we         <= '0';
        alu_out_we    <= '1';
        tg_we         <= '0';
        alu_op        <= aluop;
        m_opd_b_sel   <= "01";
        m_opd_a_sel   <= '1';
        opd_b_we      <= '0';
        opd_a_we      <= '0';
        rf_we         <= '0';
        m_rd_sel      <= '0';

      -- WRITE BACK I-TYPE
      when "0101" =>
        m_pc_din_sel  <= "00";
        cu_zero       <= '0';
        branch        <= '0';
        pc_we_s       <= '0';
        ca_addr_sel   <= "00";
        ca_we         <= '1';
        m_cc_addr_sel <= '0';
        cc_rw         <= '0';
        cc_cs         <= '0';
        md_we         <= '0';
        m_data_sel    <= '1';
        ir_we         <= '0';
        alu_out_we    <= '0';
        tg_we         <= '0';
        alu_op        <= "00";
        m_opd_b_sel   <= "00";
        m_opd_a_sel   <= '0';
        opd_b_we      <= '0';
        opd_a_we      <= '0';
        rf_we         <= '1';
        m_rd_sel      <= '0';

      -- EXEC R-TYPE
      when "1011" =>
        m_pc_din_sel  <= "00";
        cu_zero       <= '0';
        branch        <= '0';
        pc_we_s       <= '0';
        ca_addr_sel   <= "00";
        ca_we         <= '0';
        m_cc_addr_sel <= '0';
        cc_rw         <= '0';
        cc_cs         <= '0';
        md_we         <= '0';
        m_data_sel    <= '0';
        ir_we         <= '0';
        alu_out_we    <= '1';
        tg_we         <= '0';
        alu_op        <= aluop;
        m_opd_b_sel   <= "00";
        m_opd_a_sel   <= '1';
        opd_b_we      <= '0';
        opd_a_we      <= '0';
        rf_we         <= '0';
        m_rd_sel      <= '0';

      -- WRITE BACK R-TYPE
      when "1010" =>
        m_pc_din_sel  <= "00";
        cu_zero       <= '0';
        branch        <= '0';
        pc_we_s       <= '0';
        ca_addr_sel   <= "00";
        ca_we         <= '1';
        m_cc_addr_sel <= '0';
        cc_rw         <= '0';
        cc_cs         <= '0';
        md_we         <= '0';
        m_data_sel    <= '1';
        ir_we         <= '0';
        alu_out_we    <= '0';
        tg_we         <= '0';
        alu_op        <= "00";
        m_opd_b_sel   <= "00";
        m_opd_a_sel   <= '0';
        opd_b_we      <= '0';
        opd_a_we      <= '0';
        rf_we         <= '1';
        m_rd_sel      <= '1';

      -- MEM ACCESS READ
      when "1111" =>
        m_pc_din_sel  <= "00";
        cu_zero       <= '0';
        branch        <= '0';
        pc_we_s       <= '0';
        ca_addr_sel   <= "01";
        ca_we         <= '0';
        m_cc_addr_sel <= '1';
        cc_rw         <= '0';
        cc_cs         <= '1';
        md_we         <= '1';
        m_data_sel    <= '0';
        ir_we         <= '0';
        alu_out_we    <= '0';
        tg_we         <= '0';
        alu_op        <= "00";
        m_opd_b_sel   <= "00";
        m_opd_a_sel   <= '0';
        opd_b_we      <= '0';
        opd_a_we      <= '0';
        rf_we         <= '0';
        m_rd_sel      <= '0';

      -- WRITE BACK LOAD
      when "1001" =>
        m_pc_din_sel  <= "00";
        cu_zero       <= '0';
        branch        <= '0';
        pc_we_s       <= '0';
        ca_addr_sel   <= "00";
        ca_we         <= '1';
        m_cc_addr_sel <= '0';
        cc_rw         <= '0';
        cc_cs         <= '0';
        md_we         <= '0';
        m_data_sel    <= '0';
        ir_we         <= '0';
        alu_out_we    <= '0';
        tg_we         <= '0';
        alu_op        <= "00";
        m_opd_b_sel   <= "00";
        m_opd_a_sel   <= '0';
        opd_b_we      <= '0';
        opd_a_we      <= '0';
        rf_we         <= '1';
        m_rd_sel      <= '0';

      -- MEM ACCESS WRITE
      when "0110" =>
        m_pc_din_sel  <= "00";
        cu_zero       <= '0';
        branch        <= '0';
        pc_we_s       <= '0';
        ca_addr_sel   <= "01";
        ca_we         <= '0';
        m_cc_addr_sel <= '1';
        cc_rw         <= '1';
        cc_cs         <= '1';
        md_we         <= '0';
        m_data_sel    <= '0';
        ir_we         <= '0';
        alu_out_we    <= '0';
        tg_we         <= '0';
        alu_op        <= "00";
        m_opd_b_sel   <= "00";
        m_opd_a_sel   <= '0';
        opd_b_we      <= '0';
        opd_a_we      <= '0';
        rf_we         <= '0';
        m_rd_sel      <= '0';

      -- EXEC BRANCH
      when "0010" =>
        m_pc_din_sel  <= "00";
        cu_zero       <= cuzero;
        branch        <= '1';
        pc_we_s       <= '0';
        ca_addr_sel   <= "00";
        ca_we         <= '1';
        m_cc_addr_sel <= '0';
        cc_rw         <= '0';
        cc_cs         <= '0';
        md_we         <= '0';
        m_data_sel    <= '0';
        ir_we         <= '0';
        alu_out_we    <= '0';
        tg_we         <= '0';
        alu_op        <= aluop;
        m_opd_b_sel   <= "00";
        m_opd_a_sel   <= '1';
        opd_b_we      <= '0';
        opd_a_we      <= '0';
        rf_we         <= '0';
        m_rd_sel      <= '0';

      -- EXEC JUMP
      when "1000" =>
        m_pc_din_sel  <= "10";
        cu_zero       <= '0';
        branch        <= '0';
        pc_we_s       <= '1';
        ca_addr_sel   <= "11";
        ca_we         <= '1';
        m_cc_addr_sel <= '0';
        cc_rw         <= '0';
        cc_cs         <= '0';
        md_we         <= '0';
        m_data_sel    <= '0';
        ir_we         <= '0';
        alu_out_we    <= '0';
        tg_we         <= '0';
        alu_op        <= "00";
        m_opd_b_sel   <= "00";
        m_opd_a_sel   <= '0';
        opd_b_we      <= '0';
        opd_a_we      <= '0';
        rf_we         <= '0';
        m_rd_sel      <= '0';

        -- REFRESH CURRENT ADDRESS
      when "1101" =>
        m_pc_din_sel  <= "00";
        cu_zero       <= '0';
        branch        <= '0';
        pc_we_s       <= '0';
        ca_addr_sel   <= "00";
        ca_we         <= '1';
        m_cc_addr_sel <= '0';
        cc_rw         <= '0';
        cc_cs         <= '0';
        md_we         <= '0';
        m_data_sel    <= '0';
        ir_we         <= '0';
        alu_out_we    <= '0';
        tg_we         <= '0';
        alu_op        <= "00";
        m_opd_b_sel   <= "00";
        m_opd_a_sel   <= '0';
        opd_b_we      <= '0';
        opd_a_we      <= '0';
        rf_we         <= '0';
        m_rd_sel      <= '0';

      -- AFTER SW
      when "0100" =>
        m_pc_din_sel  <= "00";
        cu_zero       <= '0';
        branch        <= '0';
        pc_we_s       <= '0';
        ca_addr_sel   <= "00";
        ca_we         <= '1';
        m_cc_addr_sel <= '1';
        cc_rw         <= '0';
        cc_cs         <= '0';
        md_we         <= '0';
        m_data_sel    <= '0';
        ir_we         <= '0';
        alu_out_we    <= '0';
        tg_we         <= '0';
        alu_op        <= "00";
        m_opd_b_sel   <= "00";
        m_opd_a_sel   <= '0';
        opd_b_we      <= '0';
        opd_a_we      <= '0';
        rf_we         <= '0';
        m_rd_sel      <= '0';

      -- ACT LIKE IDLE
      when others =>
        m_pc_din_sel  <= "00";
        cu_zero       <= '0';
        branch        <= '0';
        pc_we_s       <= '0';
        ca_addr_sel   <= "00";
        ca_we         <= '0';
        m_cc_addr_sel <= '0';
        cc_rw         <= '0';
        cc_cs         <= '0';
        md_we         <= '0';
        m_data_sel    <= '0';
        ir_we         <= '0';
        alu_out_we    <= '0';
        tg_we         <= '0';
        alu_op        <= "00";
        m_opd_b_sel   <= "00";
        m_opd_a_sel   <= '0';
        opd_b_we      <= '0';
        opd_a_we      <= '0';
        rf_we         <= '0';
        m_rd_sel      <= '0';
        
    end case;

  end process comb;
-------------------------------------------------------------------------------
end rtl;
